<!--

@author: pan
@createDate: 2023-09-13 21:01
-->
<script setup lang="ts">
import { computed, ref, toRefs } from 'vue'
import type {
  FormItemConfig,
  StringParamStringReturnFun,
  DynFormData,
  ElFormProps,
  VueComp,
  CustomCompPropsFactory,
  UpdateRowColCountFun,
  ElFormInstance,
} from './typing'

import { useResizeObserverHook } from './ResizeObserverHook.ts'
import { watch } from 'vue'

const props = withDefaults(
  defineProps<{
    /**
根据dom宽度，计算每行能容纳的单元格数量
```
function demo(domOffsetWidth:number){
  if (domOffsetWidth <= 768) {
    return 1
  } else if (domOffsetWidth <= 992) {
    return 2
  } else if (domOffsetWidth <= 1300) {
    return 3
  } else {
    return 4
  }
}
```
     */
    updateRowColCountFun: UpdateRowColCountFun
    /**
     * 是否需要显示toggle按钮(一般是展开/收缩按钮)(v-model)[由组件内部设置，外部只要根据实际值，做对应的逻辑即可]
     */
    needShowToggleBtn?: boolean
    /**
     * 表单是否处于展开状态
     */
    expand?: boolean
    /**
     * 表单项配置
     */
    formItemArr?: FormItemConfig[]
    /**
     * 表单数据对象.(v-model)
     * p.s. 必须通过v-model方式使用，否则会导致表单项值无法改变
     */
    formData?: DynFormData
    /**
     * 支持el-form除model外的所有props属性
     */
    elFormProps?: ElFormProps
    /**
     * 主要用于做i18n的转换
     */
    formItemLabelConvertFun?: StringParamStringReturnFun
    /**
     * 主要用于做i18n的转换
     */
    formItemPlaceholderConvertFun?: StringParamStringReturnFun
    /**
     * 表单项组件
     */
    formItemCompNameMap?: Map<string, VueComp>
  }>(),
  {
    formItemArr: () => [],
    formData: () => ({}),
    elFormProps: undefined,
    formItemLabelConvertFun: (str?: string) => (str ? str : ''),
    formItemPlaceholderConvertFun: (str?: string) => (str ? str : ''),
    formItemCompNameMap: undefined,
    expand: false,
  }
)
const { formItemCompNameMap } = props
const { formData, formItemArr, expand } = toRefs(props)
const $emits = defineEmits<{
  /**
   * v-model:formData
   */
  'update:formData': [val: DynFormData]
  /**
   * v-model:needShowToggleBtn
   */
  'update:needShowToggleBtn': [val: boolean]
}>()
const {
  colCount,
  itemWidth,
  formWrapperRef,
  realCellCount,
  needShowToggleBtn: _needShowToggleBtn,
} = useResizeObserverHook(3, props.updateRowColCountFun)
watch(
  () => formItemArr.value.length,
  (newFormItemArrCount: number) => {
    realCellCount.value = newFormItemArrCount
  },
  { immediate: true }
)
const formDataComputed = computed<DynFormData>({
  get() {
    return formData.value
  },
  set(val: DynFormData) {
    $emits('update:formData', val)
  },
})
watch(
  _needShowToggleBtn,
  val => {
    $emits('update:needShowToggleBtn', val)
  },
  { immediate: true }
)
function loadFormItemComp(name: string) {
  if (!formItemCompNameMap) {
    throw new Error(`动态表单所需的[${name}]组件未找到或不存在!`)
  }
  const comp = formItemCompNameMap.get(name)
  if (!comp) {
    throw new Error(`动态表单所需的[${name}]组件未找到或不存在!`)
  }
  return comp
}
/**
 * 存放表单项本身显示/隐藏标识
 */
const formItemShowHideFlagArr: boolean[] = []
function formItemIsNeedShow(formItem: FormItemConfig, formData: DynFormData, idx: number) {
  const { show = true } = formItem
  let ret = false
  if (typeof show === 'boolean') {
    ret = show
  } else {
    ret = show(formItem, formData)
  }
  formItemShowHideFlagArr[idx] = ret
  let alreadyShowCount = 0
  for (let i = 0; i <= idx; i++) {
    if (formItemShowHideFlagArr[idx]) {
      alreadyShowCount += 1
    }
  }
  if (expand.value) {
    return ret
  } else {
    return ret && alreadyShowCount < colCount.value
  }
}

function formItemIsNeedDisable(formItem: FormItemConfig, formData: DynFormData) {
  const { disable = false } = formItem
  if (typeof disable === 'boolean') {
    return disable
  } else {
    return disable(formItem, formData)
  }
}

function loadFormRules(formItem: FormItemConfig, formData: DynFormData) {
  const { rules } = formItem
  if (typeof rules === 'function') {
    return rules(formItem, formData)
  } else {
    return rules
  }
}
function buildCustomCompProps(customCompProps?: Record<string, unknown> | CustomCompPropsFactory) {
  if (customCompProps instanceof Function) {
    return customCompProps()
  } else {
    return customCompProps
  }
}
defineSlots<{
  /**
   * 写点注释
   */
  extraElFormItems: []
}>()
const elFormInstance = ref<ElFormInstance>()
defineExpose({
  /**
   * el-form实例
   */
  elFormInstance,
})
</script>

<template>
  <div ref="formWrapperRef" class="o-dyn-form-wrapper">
    <el-form ref="elFormInstance" v-bind="elFormProps" :model="formDataComputed">
      <template v-for="(formItem, idx) in formItemArr" :key="formItem.prop">
        <el-form-item
          v-if="formItemIsNeedShow(formItem, formDataComputed, idx)"
          v-bind="formItem.elFormItemProps"
          :label="props.formItemLabelConvertFun(formItem.label as string)"
          :prop="formItem.prop"
          :rules="loadFormRules(formItem, formDataComputed)"
        >
          <component
            :is="loadFormItemComp(formItem.name)"
            v-model="formDataComputed[formItem.prop]"
            :form-data="formDataComputed"
            :disabled="formItemIsNeedDisable(formItem, formDataComputed)"
            :placeholder="formItemPlaceholderConvertFun(formItem.placeholder)"
            :item-config="formItem"
            v-bind="buildCustomCompProps(formItem.customCompProps)"
          ></component>
        </el-form-item>
      </template>
      <slot name="extraElFormItems"></slot>
    </el-form>
  </div>
</template>

<style lang="scss" scoped>
.o-dyn-form-wrapper {
  ::v-deep(> form > div) {
    margin: 0;
    padding-right: 32px;
    width: v-bind(itemWidth);
    box-sizing: border-box;
  }
}
</style>
